﻿using System;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.Circuits.Basis;
using TSU.FAMC.ComputerEngineering.Classes.Enums;
using System.IO;
using TSU.FAMC.ComputerEngineering.Classes.OrandTrees.Basis;

namespace TSU.FAMC.ComputerEngineering.Classes.OrandTrees.Readers
{
    /// <summary>
    /// Builds ORANDTree object by Circuit object analyzing.
    /// </summary>
    public class OrandTreeReaderCircuit : OrandTreeReader
    {
        // Propertis

        /// <summary>
        /// Input circuit object to read.
        /// </summary>
        //public Circuit Input;
        public Circuits.Circuit Input;


        // Methods

        /// <summary>
        /// Reads ORANDTree object.
        /// </summary>
        public override void Read()
        {
            if (ReferenceEquals(Input, null))
            {
                throw new InvalidOperationException("Input circuit must be specified.");
            }

            // instanciating tree
            Tree = new OrandTree();

            // building tree by rounding circuit from outputs to inputs
            foreach (var output in Input.Outputs)
            {
                var top = new Vertex();
                Tree.Tops.Add(top);

                // making round for current output
                DoRound(Input, output as Element, top);
            }

            // simplifying tree
            Tree.Simplify();

            // checking tree consistency
            CheckConsistency(Tree);
        }

        /// <summary>
        /// Checks ORAND-tree consistency.
        /// </summary>
        /// <param name="tree">ORANDTree to check.</param>
        private static void CheckConsistency(OrandTree tree)
        {
            if (tree == null) throw new ArgumentNullException("tree");

            // searching for pending leaves
            if (tree.Leaves.Any(leaf => leaf.Type != VertexType.Input || leaf.Parents.Count == 0 && tree.Tops.IndexOf(leaf) == -1))
            {
                throw new InvalidDataException("ORAND-tree consistency checking failed. Found pending leaf.");
            }
        }

        /// <summary>
        /// Circuit rounding for tree building.
        /// </summary>
        /// <param name="circuit">Rounding circuit.</param>
        /// <param name="currentElement">Current rounding circuit's element.</param>
        /// <param name="parent">Built tree vertex.</param>
        private void DoRound(Circuits.Circuit circuit, Element currentElement, Vertex parent)
        {
            if (!(currentElement is Gate))
            {
                // appending element data
                if (!string.IsNullOrEmpty(currentElement.Name))
                {
                    // TODO: unit test
                    if (parent.Data == null)
                    {
                        parent.Data = currentElement.Name;
                    }
                    else
                    {
                        // appending element name
                        parent.Data = (String)parent.Data + "," + currentElement.Name;
                    }
                }


                // going deeper
                foreach (var input in currentElement.Inputs)
                {
                    DoRound(circuit, input as Element, parent);
                }
                return;
            }

            var currentVertex = new Vertex();
            if (currentElement is AndGate)
            {
                currentVertex.Type = VertexType.And;
            }
            else if (currentElement is OrGate)
            {
                currentVertex.Type = VertexType.Or;
            }
            else if (currentElement is NAndGate)
            {
                currentVertex.Type = VertexType.And;
                currentVertex.IsInverted = true;
            }
            else if (currentElement is NOrGate)
            {
                currentVertex.Type = VertexType.Or;
                currentVertex.IsInverted = true;
            }
            else if (currentElement is NotGate)
            {
                currentVertex.IsInverted = true;
            }
            else if (currentElement is Constant0Gate)
            {
                currentVertex.Type = VertexType.Constant0;
            }
            else if (currentElement is Constant1Gate)
            {
                currentVertex.Type = VertexType.Constant1;
            }


            // ready to add vertex into the tree

            if (parent.Type == VertexType.Undefined && parent.Parents.Count == 0)
            {
                // a top of the tree has not been determined
                parent.Type = currentVertex.Type;
                parent.IsInverted = currentVertex.IsInverted ^ parent.IsInverted;
                if (parent.Data == null)
                {
                    parent.Data = currentElement.Name;
                }
                else
                {
                    // appending element name
                    parent.Data = (String)parent.Data + "," + currentElement.Name;
                }
            }
            else
            {
                // connecting new vertex with parent using an edge
                var edge = new Edge {Data = currentElement.Name};

                // setting name of the element as an edge data

                // making links
                edge.Parents.Add(parent);
                parent.Children.Add(edge);
                edge.Children.Add(currentVertex);
                currentVertex.Parents.Add(edge);

                parent = currentVertex;
            }


            if (currentElement.Inputs.Count > 0)
            {
                // going to inputs recursively
                foreach (var input in currentElement.Inputs)
                {
                    DoRound(circuit, input as Element, parent);
                }

                return;
            }

            if (circuit.Inputs.IndexOf(currentElement) >= 0)
            {
                // primary input of the circuit
                parent.Type = VertexType.Input;
                Tree.Leaves.Add(parent);
            }
        }
    }
}
